<?php
/**
 * Contains the Event class and includes all the right files.
 *
 * @package    event
 * @subpackage event
 * @author 	   Gerrit Jan van Ahee <gerrit.jan@yes2web.nl>
 * @copyright  2010-2011 Yes2web B.V.
 * @license    MIT License http://www.opensource.org/licenses/mit-license.php
 * @version    1.0.1
 * @link 	   www.yes2web.nl
 * @see        Event\Handler, Event\Exception
 */
namespace Event;

// require the proper event classes
require __DIR__.DIRECTORY_SEPARATOR.'handler.class.php';
require __DIR__.DIRECTORY_SEPARATOR.'exception.class.php';
// require the log package
$sEventPackageDir = __DIR__.DIRECTORY_SEPARATOR.'log'.DIRECTORY_SEPARATOR;
require $sEventPackageDir.'device.class.php';
require $sEventPackageDir.'handler.class.php';
require $sEventPackageDir.'message.class.php';
unset($sEventPackageDir);

/**
 * The Event class. Upon constructing an Event_Event the new instance will
 * automatically fire. This is done by signaling the Event_Handler statically about
 * its existence. This will in turn notify the actual event handlers that a
 * potentially interesting event has taken place. This implies that triggering events
 * is done by simply constucting an Event_Event:
 * <code>
 * // constructs the event and signals potential handlers
 * new Event\Event('event-name'); 
 * </code>
 *
 * Classes triggering events need not extends specific classes, making this design
 * more flexible than e.g. the Dispatcher-Observer type patterns. The Event_Event
 * will determine itself who has triggered it, and will provide this information to
 * the Event_Handler when needed.
 * 
 * When an event is triggered by an instance, i.e. from a non-static method call,
 * that instance is available through the {@link getSource()} method. When triggered
 * during a static method call, this method provides the name (String) of the
 * triggering class.
 * 
 * Besides the instance triggering the Event, the arguments provided to the method
 * triggering the event can also be accessed through the {@link getArgs} method.
 * <code>
 * // no need to extend anything, allowing you to extend whatever
 * // else you would really like to
 * class TriggeringClass
 * { 
 *  // ...
 * 	public function someTrigger($a, $b, $c){
 * 		new Event\Event('trigger');
 * 	}
 * 	// ...
 * }
 * $oTriggeringClassInstance->someTrigger('one', 2, 'THREE');
 * </code>
 * <p>
 * A call to {@link getArgs()} will provide
 * </p>
 * <code>
 * array(
 * 	'one', 2, 'THREE'
 * );
 * </code>
 * 
 * Events can be given additional information by adding an array when constructing
 * it:
 * <code>
 * new Event\Event(
 * 	'some-event',
 * 	array(
 * 		'key1' => 'value1',
 * 		'key2' => 'value2',
 * 	)
 * );
 * </code>
 *
 * which is available through the {@link getParams()} method. Populate this array
 * with any information relevant to a handler.
 * 
 * Finally, the event keeps a prevent-default flag that may be checked by the
 * triggering instance. This way a handler could prevent the default action from
 * happening (if the triggering object supports this). This is facilitated by the
 * {@link preventDefault()} and {@link isDefaultPrevented()} methods:
 * 
 * <code>
 * class TriggeringClass extends Your_Favorite_ORM {
 * 	// ...
 * 	public function delete(){
 * 		$oEvent = new Event\Event('delete');
 * 		if (false === $oEvent->isDefaultPrevented()) parent::delete();
 * 	}
 * 	// ...
 * }
 * </code>
 * 
 * An Event\Handler would simply have to call
 * <code>
 * $oEvent->preventDefault();
 * </code>
 *
 * @package    event
 * @subpackage event
 * @author 	   Gerrit Jan van Ahee <gerrit.jan@yes2web.nl>
 * @copyright  2010-2011 Yes2web B.V.
 * @license    MIT License http://www.opensource.org/licenses/mit-license.php
 * @version    1.0.1
 * @link 	   www.yes2web.nl
 * @see        Event\Handler, Event\Exception
 */
class Event {
	/**
	 * Shorthand for triggering an Event.
	 *
	 * This method provides a slightly simpler/faster/easier way to trigger an
	 * Event (faster as is: less code). This mostly applies to situations where you
	 * would like to use the prevent default flag:
	 * <code>
	 * 	if (false === \Event\Event::trigger('before-delete')->isDefaultPrevented()) {
	 * 		// delete
	 * 		new \Event\Event('delete'); // no "default", so don't care
	 * 	}
	 * 	else {
	 * 		// one of the event handlers wished the deletion to be prevented
	 * 	}
	 * </code>
	 *
	 * Off course, the same can be done using
	 * <code>
	 * $oEvent = new \Event\Event('before-delete');
	 * if (false === $oEvent->isDefaultPrevented()) { ... }
	 * </code>
	 * but for all us lazy people out there...
	 *
	 * @param String $sType   Type of the event
	 * @param array  $amParam additional parameters, default empty list
	 * @param array  $amTrace trace to this call, defaults to debug_backtrace()
	 * @return \Event\Event The triggered event
	 */
	public static function trigger($sType, array $amParam = null){
		$amTrace = debug_backtrace();
		return new \Event\Event($sType, $amParam, $amTrace);
	}
	
	/**
	 * @var Mixed Object or String depending on triggering method: static: String
	 * (classname), other: Object (instance)
	 */
	protected $mSource;
	/**
	 * @var String identifier of the type of this event
	 */
	protected $sType;
	/**
	 * @var Object[] list of additional values provided to this event
	 */
	protected $amParam;
	/**
	 * @var Mixed[] the arguments that were provided to the source when called at the
	 * time the event was triggered
	 */
	protected $amArgv = array();
	/**
	 * @var Mixed[] debug trace
	 */
	protected $amTrace;
	/**
	 * Handlers can signal triggerers to prevent their default action, however they
	 * will have to check this
	 * first.
	 * 
	 * @see preventDefault()
	 * @see isDefaultPrevented()
	 * @var bool prevent default? 
	 */
	protected $bDefaultPrevented = false;
	
	/**
	 * Construct an new Event by providing at least a type
	 * 
	 * @param String $sType   Type of the event
	 * @param array  $amParam additional parameters, default empty list
	 * @param array  $amTrace trace to this call, defaults to debug_backtrace()
	 */	
	public function __construct($sType, array $amParam = null, array $amTrace = null){		
		$this->sType = $sType;
		$this->amParam = $amParam?:array();
		$this->amTrace = $amTrace?:debug_backtrace();
		
		$amSource = array_shift($this->amTrace);	// remove reference to this call
		$amSource = array_shift($this->amTrace);	// remove reference to source
		
		// retrieve the source from trace
		if (isset($amSource['object'])) {
			// not present when the event originates from a static call
			$this->mSource = $amSource['object'];
		}
		elseif (isset($amSource['class'])) {
			$this->mSource = $amSource['class'];
		}
		
		if (isset($amSource['args'])) {
			$this->amArgv = $amSource['args'];
		}
		
		Handler::signalEvent($this);
	}
	
	/**
	 * Get all args that have been passed to the method that has thrown this event
	 * 
	 * @return Mixed[] Argument vector provided to the method triggering this event
	 */
	public function getArgs(){
		return $this->amArgv;
	}
	
	/**
	 * Get the instance (or classname in case of static call) triggering this Event
	 *
	 * @return Mixed Either the instance (Object) or the classname (String)
	 */
	public function getSource(){
		return $this->mSource;
	}
	
	/**
	 * Get any additional parameters provided by the triggering instance/object (the
	 * Source)
	 * 
	 * @return Mixed[] list of parameters
	 */
	public function getParams(){
		return $this->amParam;
	}
	
	/**
	 * Get the type of $this Event
	 *
	 * @return String The name of this event
	 */
	public function getType(){
		return $this->sType;
	}
	
	/**
	 * Has some handler signalled the prevention of the default actions?
	 * 
	 * @see Event_Event::preventDefault
	 * @return bool Should the default action be prevented by the triggering method
	 */
	public function isDefaultPrevented(){
		return $this->bDefaultPrevented;
	}
	/**
	 * Raise the flag that the default action following the event should be prevented
	 *
	 * The flag can only be raised, never lowered
	 * 
	 * @return bool Should the default action be prevented by the triggering method
	 */
	public function preventDefault(){
		$this->bDefaultPrevented = true;
		return $this;
	}
}